package cc.iotkit.plugins.modbus.service;

import cc.iotkit.common.utils.JsonUtils;
import cc.iotkit.plugin.core.IPluginConfig;
import cc.iotkit.plugin.core.IPluginScript;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.up.PropertyReport;
import cc.iotkit.plugins.modbus.conf.ModbusConfig;
import cc.iotkit.script.IScriptEngine;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.digitalpetri.modbus.requests.*;
import com.digitalpetri.modbus.responses.*;
import com.digitalpetri.modbus.slave.ModbusTcpSlave;
import com.digitalpetri.modbus.slave.ModbusTcpSlaveConfig;
import com.digitalpetri.modbus.slave.ServiceRequestHandler;
import com.fasterxml.jackson.core.type.TypeReference;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.bootstrap.realize.PluginCloseListener;
import com.gitee.starblues.core.PluginCloseType;
import com.gitee.starblues.core.PluginInfo;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.IntStream;

/**
 * @author sjg
 */
@Slf4j
@Service
public class ModbusPlugin implements PluginCloseListener {

    @Autowired
    private PluginInfo pluginInfo;

    @Autowired
    @AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
    private IPluginScript pluginScript;

    @Autowired
    @AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
    private IThingService thingService;

    private IScriptEngine scriptEngine;

    @Autowired
    private ModbusConfig modbusConfig;

    @Autowired
    @AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
    private IPluginConfig pluginConfig;

    // 产品Key
    private String productKey;

    private List<String> deviceNames = new ArrayList<>();

    private ModbusTcpSlaveConfig config;
    private ModbusTcpSlave slave;
    private CompletableFuture<ModbusTcpSlave> bind;

    private final Set<Integer> registeredDevice = new HashSet<>();

    private int[] slaves;

    private final Map<Integer, String> DATA_CACHE = new HashMap<>();

    @PostConstruct
    public void init() {
        // 新生成一个插件配置MAP
        Map<String, Object> modBusconfig = pluginConfig.getConfig(pluginInfo.getPluginId());
        log.info("get config:{}", JsonUtils.toJsonString(modBusconfig));
        // 拷贝modbusConfig进刚创建的MAP中
        BeanUtil.copyProperties(modBusconfig, modbusConfig, CopyOptions.create().ignoreNullValue());

        config = new ModbusTcpSlaveConfig.Builder().build();
        slave = new ModbusTcpSlave(config);

        if(0 < modBusconfig.size()){
            productKey = modbusConfig.getProductKey();
            deviceNames = Arrays.asList(modbusConfig.getDeviceName().split(","));

            bind = slave.bind(modbusConfig.getHost(), modbusConfig.getPort());
            log.info("binded ip " + modbusConfig.getHost());
        } else {
            // 产品Key
            productKey= new String("mrpi2yDSn7z2Kznp");
            deviceNames.add(new String("23032890"));

            bind = slave.bind("127.0.0.1", 502);
            log.info("modbus-tcp-slave plugin config info is null!");
        }
        bind.thenAccept(slave -> System.out.println("bind ip and port complete!"));

        slaves = IntStream.rangeClosed(0, deviceNames.size()-1).toArray();
        //获取脚本引擎
        scriptEngine = pluginScript.getScriptEngine(pluginInfo.getPluginId());
    }

//    @Scheduled(initialDelay = 3000, fixedDelay = 1000)
//    public void taskRead() {
//        for (int slave : slaves) {
//            CompletableFuture<ReadHoldingRegistersResponse> future =
//                    master.sendRequest(new ReadHoldingRegistersRequest(0+slave, 10), 1);
//
//            future.thenAccept(response -> {
//                String rspBytes = ByteBufUtil.hexDump(response.getRegisters());
//                ReferenceCountUtil.release(response);
//
//                log.info("receive Response: " + rspBytes);
//                //相同数据不处理
//                if (DATA_CACHE.getOrDefault(slave+1, "").equals(rspBytes)) {
//                    return;
//                }
//                DATA_CACHE.put(slave+1, rspBytes);
//
//                if (!registeredDevice.contains(slave+1)) {
//                    //第一次读取自动注册设备
//                    thingService.post(pluginInfo.getPluginId(), DeviceRegister.builder()
//                            .id(UUID.randomUUID().toString())
//                            .productKey(productKey)
//                            .deviceName(deviceNames.get(slave))
//                            .build());
//                    registeredDevice.add(slave+1);
//                    //并上线
//                    thingService.post(pluginInfo.getPluginId(), DeviceStateChange.builder()
//                            .id(UUID.randomUUID().toString())
//                            .productKey(productKey)
//                            .deviceName(deviceNames.get(slave))
//                            .state(DeviceState.ONLINE)
//                            .build());
//                }
//
//                //调用脚本解码
//                Map<String, Object> rst = scriptEngine.invokeMethod(new TypeReference<>() {
//                }, "decode", rspBytes);
//                if (rst == null || rst.isEmpty()) {
//                    log.info("null");
//                    return;
//                }
//
//                //属性上报
//                thingService.post(pluginInfo.getPluginId(), PropertyReport.builder()
//                        .id(UUID.randomUUID().toString())
//                        .productKey(productKey)
//                        .deviceName(deviceNames.get(slave))
//                        .params(rst)
//                        .build());
//            });
//        }
//    }

    @Override
    public void close(GenericApplicationContext applicationContext, PluginInfo pluginInfo, PluginCloseType closeType) {
        try {
            slave.shutdown();
            if (closeType == PluginCloseType.UNINSTALL) {
                log.info("modbus plugin UNINSTALL：{}", pluginInfo.getPluginId());
            } else if (closeType == PluginCloseType.STOP) {
                log.info("modbus plugin STOP：{}", pluginInfo.getPluginId());
            } else if (closeType == PluginCloseType.UPGRADE_UNINSTALL) {
                log.info("modbus plugin UPGRADE_UNINSTALL：{}", pluginInfo.getPluginId());
            }
        } catch (Throwable e) {
            log.error("modbus plugin stop error", e);
        }
    }

    @Scheduled(initialDelay = 3000, fixedDelay = 1000)
    public void start() throws ExecutionException, InterruptedException {
        slave.setRequestHandler(new ServiceRequestHandler() {
            // 下位机请求读单保持寄存器的数据，上位机就返回如下数据
            @Override
            public void onReadHoldingRegisters(ServiceRequest<ReadHoldingRegistersRequest, ReadHoldingRegistersResponse> service) {
                String clientRemoteAddress = service.getChannel().remoteAddress().toString();
                String clientIp = clientRemoteAddress.replaceAll(".*/(.*):.*", "$1");
                String clientPort = clientRemoteAddress.replaceAll(".*:(.*)", "$1");

                ReadHoldingRegistersRequest request = service.getRequest();

                ByteBuf registers = PooledByteBufAllocator.DEFAULT.buffer(request.getQuantity());

                for (int i = 0; i < request.getQuantity(); i++) {
                    registers.writeShort(i);
                }

                service.sendResponse(new ReadHoldingRegistersResponse(registers));

                ReferenceCountUtil.release(request);
            }

            @Override
            public void  onWriteSingleCoil(ServiceRequest<WriteSingleCoilRequest, WriteSingleCoilResponse> service) {
                String clientRemoteAddress = service.getChannel().remoteAddress().toString();
                String clientIp = clientRemoteAddress.replaceAll(".*/(.*):.*", "$1");
                String clientPort = clientRemoteAddress.replaceAll(".*:(.*)", "$1");

                WriteSingleCoilRequest request = service.getRequest();

                int value = request.getValue();

                Map<String, Object> rst = new HashMap<>();
                rst.put("Count", value);

                //属性上报
                thingService.post(pluginInfo.getPluginId(), PropertyReport.builder()
                        .id(UUID.randomUUID().toString())
                        .productKey(productKey)
                        .deviceName(deviceNames.get(0))
                        .params(rst)
                        .build());

                service.sendResponse(new WriteSingleCoilResponse(request.getAddress(), request.getValue()));

                ReferenceCountUtil.release(request);
            }

            @Override
            public void  onWriteSingleRegister(ServiceRequest<WriteSingleRegisterRequest, WriteSingleRegisterResponse> service) {
                String clientRemoteAddress = service.getChannel().remoteAddress().toString();
                String clientIp = clientRemoteAddress.replaceAll(".*/(.*):.*", "$1");
                String clientPort = clientRemoteAddress.replaceAll(".*:(.*)", "$1");

                WriteSingleRegisterRequest request = service.getRequest();

                int value = request.getValue();


                Map<String, Object> rst = scriptEngine.invokeMethod(new TypeReference<>() {
                }, "yqybCY", value);

                //属性上报
                thingService.post(pluginInfo.getPluginId(), PropertyReport.builder()
                        .id(UUID.randomUUID().toString())
                        .productKey(productKey)
                        .deviceName(deviceNames.get(0))
                        .params(rst)
                        .build());

                service.sendResponse(new WriteSingleRegisterResponse(request.getAddress(), request.getValue()));

                ReferenceCountUtil.release(request);
            }

            @Override
            public void  onWriteMultipleRegisters(ServiceRequest<WriteMultipleRegistersRequest, WriteMultipleRegistersResponse> service) {
                WriteMultipleRegistersRequest request = service.getRequest();

                int quantity = request.getQuantity();
                ByteBuf buff = request.getValues();
                List<Integer> values = new ArrayList<Integer>();

                for(int i=0; i<quantity; i++) {
                    values.add((int)buff.getShort(2*i));
                }

                Map<String, Object> rst = scriptEngine.invokeMethod(new TypeReference<>() {
                }, "yqybCY", values);

                //属性上报
                thingService.post(pluginInfo.getPluginId(), PropertyReport.builder()
                        .id(UUID.randomUUID().toString())
                        .productKey(productKey)
                        .deviceName(deviceNames.get(request.getAddress()))
                        .params(rst)
                        .build());

                service.sendResponse(new WriteMultipleRegistersResponse(request.getAddress(), request.getQuantity()));

                ReferenceCountUtil.release(request);

            }
        });


    }

//    @Scheduled(initialDelay = 3000, fixedDelay = 1000)
//    public void taskWrite() {
//        for (int slave : slaves) {
//            CompletableFuture<ReadHoldingRegistersResponse> future =
//                    master.sendRequest(new ReadHoldingRegistersRequest(0+slave, 10), 1);
//
//            future.thenAccept(response -> {
//                String rspBytes = ByteBufUtil.hexDump(response.getRegisters());
//                ReferenceCountUtil.release(response);
//
//                log.info("receive Response: " + rspBytes);
//                //相同数据不处理
//                if (DATA_CACHE.getOrDefault(slave+1, "").equals(rspBytes)) {
//                    return;
//                }
//                DATA_CACHE.put(slave+1, rspBytes);
//
//                if (!registeredDevice.contains(slave+1)) {
//                    //第一次读取自动注册设备
//                    thingService.post(pluginInfo.getPluginId(), DeviceRegister.builder()
//                            .id(UUID.randomUUID().toString())
//                            .productKey(productKey)
//                            .deviceName(deviceNames.get(slave))
//                            .build());
//                    registeredDevice.add(slave+1);
//                    //并上线
//                    thingService.post(pluginInfo.getPluginId(), DeviceStateChange.builder()
//                            .id(UUID.randomUUID().toString())
//                            .productKey(productKey)
//                            .deviceName(deviceNames.get(slave))
//                            .state(DeviceState.ONLINE)
//                            .build());
//                }
//
//                //调用脚本解码
//                Map<String, Object> rst = scriptEngine.invokeMethod(new TypeReference<>() {
//                }, "decode", rspBytes);
//                if (rst == null || rst.isEmpty()) {
//                    log.info("null");
//                    return;
//                }
//
//                //属性上报
//                thingService.post(pluginInfo.getPluginId(), PropertyReport.builder()
//                        .id(UUID.randomUUID().toString())
//                        .productKey(productKey)
//                        .deviceName(deviceNames.get(slave))
//                        .params(rst)
//                        .build());
//            });
//        }
//    }
}
